/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package app;

import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import libs.*;

/**
 *
 * @author Dejvino
 */
public class AttendanceImpl implements Attendance{

    private HashMap<Student, LinkedHashSet<Date>> absences;
    private HashMap<Student, LinkedHashSet<Date>> presences;
    private HashMap<Student, Seminar> studentToSeminar;
    private HashMap<Seminar, LinkedHashSet<Student>> seminarToStudents;
    private Calendar origCalendar;
    private Calendar cleanCalendar;

    public AttendanceImpl()
    {
        absences = new HashMap<Student, LinkedHashSet<Date>>();
        presences = new HashMap<Student, LinkedHashSet<Date>>();
        studentToSeminar = new HashMap<Student, Seminar>();
        seminarToStudents = new HashMap<Seminar, LinkedHashSet<Student>>();
        origCalendar = new GregorianCalendar();
        cleanCalendar = new GregorianCalendar();
    }

    /**
     *          Clears unwanted data from date to make the set with dates "searchable"
     *
     * @param   date to clear
     * @return  cleared date
     */
    private Date clearDate(Date date)
    {
        if(date == null)
            throw new NullPointerException("date");
        origCalendar.setTime(date);
        cleanCalendar.clear();
        cleanCalendar.set(origCalendar.get(Calendar.YEAR),
                        origCalendar.get(Calendar.MONTH),
                        origCalendar.get(Calendar.DATE));
        return cleanCalendar.getTime();
    }

    /**
     *          Returns current clear date
     * @return
     */
    private Date clearDate()
    {
        return clearDate(new Date());
    }

    /**
     *          It sets student's absence today in specified seminar.
     *
     * @param   student     Student.
     * @param   seminar     Seminar.
     * @throws  AttendanceException if the seminar doesn't exist
     *          or the student is not registered in seminar.
     * @throws  NullPointerException if some of the arguments is null.
     */
    public void absent (Student student, Seminar seminar)
            throws AttendanceException
    {
        absent(student, seminar, clearDate());
    }

    /**
     *          It sets student's absence today in specified seminar
     *          on specified day.
     *
     * @param   student     Student.
     * @param   seminar     Seminar.
     * @param   date        Date.
     * @throws  AttendanceException if the seminar doesn't exist
     *          or the student is not registered in seminar.
     * @throws  NullPointerException if some of the arguments is null.
     */
    public void absent (Student student, Seminar seminar, Date date)
            throws AttendanceException
    {
        if(student == null)
            throw new NullPointerException("student");
        if(seminar == null)
            throw new NullPointerException("seminar");
        if(date == null)
            throw new NullPointerException("date");

        if(!studentToSeminar.keySet().contains(student))
            throw new AttendanceException("Student not registered");
        if(!seminarToStudents.keySet().contains(seminar))
            throw new AttendanceException("Seminar not registered");
        if(studentToSeminar.get(student) != seminar)
            throw new AttendanceException("Student not registered to this seminar");

        absences.get(student).add(clearDate(date));
    }

    /**
     *          It adds a seminar to the attendance book.
     *
     * @param   seminar     Seminar
     * @return  TRUE if the seminar has been added,
     *          FALSE if the seminar has already existed.
     * @throws  NullPointerException if the seminar is null.
     */
    public boolean addSeminar (Seminar seminar)
    {
        if(seminar == null)
            throw new NullPointerException("seminar");
        if(seminarToStudents.keySet().contains(seminar))
            return false;
        seminarToStudents.put(seminar, new LinkedHashSet());
        return true;
    }

    /**
     *          It adds a student to the seminar.
     *          If the student has been already registered in another seminar,
     *          his/her registration in another seminar will be canceled.
     *
     * @param   student     Student.
     * @param   seminar     Seminar.
     * @return  TRUE if the student has been added,
     *          FALSE if the student has been already registered in this seminar.
     * @throws  AttendanceException if the seminar does not exist.
     * @throws  NullPointerException if some of the arguments is null.
     */
    public boolean addStudent (Student student, Seminar seminar)
            throws AttendanceException
    {
        if(student == null)
            throw new NullPointerException("student");
        if(seminar == null)
            throw new NullPointerException("seminar");
        if(!seminarToStudents.keySet().contains(seminar))
            throw new AttendanceException("Seminar does not exist");
        if(seminarToStudents.get(seminar).contains(student))
            return false;
        seminarToStudents.get(seminar).add(student);
        studentToSeminar.put(student, seminar);
        absences.put(student, new LinkedHashSet<Date>());
        presences.put(student, new LinkedHashSet<Date>());
        return true;
    }

    /**
     *          It changes attendance on the specified day.
     *          If the student was absent, it changes on present and vica versa.
     *
     * @param   student     Student
     * @param   seminar     Student
     * @param   date        Date.
     * @throws  NullPointerException if some of the arguments is null.
     * @throws  AttendanceException if the seminar does not exist,
     *          the student is not registered in the seminar
     *          or he/she has no presence/absence on the specified day.
     */
    public void changeAttendace (Student student, Seminar seminar, Date date)
            throws AttendanceException
    {
        if(student == null)
            throw new NullPointerException("student");
        if(seminar == null)
            throw new NullPointerException("seminar");
        if(date == null)
            throw new NullPointerException("date");
        if(!seminarToStudents.keySet().contains(seminar))
            throw new AttendanceException("Seminar does not exist");
        if(!studentToSeminar.containsKey(student))
            throw new AttendanceException("Student not registered");
        if(!seminarToStudents.get(seminar).contains(student))
            throw new AttendanceException("Student not registered to this seminar");
        Date cdate = clearDate(date);
        if(absences.get(student).contains(cdate))
        {
            absences.get(student).remove(cdate);
            presences.get(student).add(cdate);
        } else if(presences.get(student).contains(cdate)) {
            presences.get(student).remove(cdate);
            absences.get(student).add(cdate);
        } else {
            throw new AttendanceException("Student has no attendance record on this date");
        }
    }

    /**
     *          It returns the absence of the students in the seminars.
     *
     * @param   seminar     Seminar.
     * @return  Students and dates when the students were absent.
     * @throws  NullPointerException if the seminar is null.
     */
    public Map<Student,Set<Date>> getAbsence (Seminar seminar)
    {
        if(seminar == null)
            throw new NullPointerException("seminar");
        Map<Student,Set<Date>> ret = new HashMap<Student,Set<Date>>();
        for(Student student : seminarToStudents.get(seminar))
        {
            ret.put(student, absences.get(student));
        }
        return ret;
    }

    /**
     *          It returns the presence of the students in the seminars.
     *
     * @param   seminar     Seminar.
     * @return  Students and dates when the students were present.
     * @throws  NullPointerException if the seminar is null.
     */
    public Map<Student,Set<Date>> getPresence (Seminar seminar)
    {
        if(seminar == null)
            throw new NullPointerException("seminar");
        Map<Student,Set<Date>> ret = new HashMap<Student,Set<Date>>();
        for(Student student : seminarToStudents.get(seminar))
        {
            ret.put(student, presences.get(student));
        }
        return ret;
    }

    /**
     *          It returns all seminar groups.
     *
     * @return  Seminar groups.
     */
    public Set<Seminar> getSeminars ()
    {
        return Collections.unmodifiableSet(seminarToStudents.keySet());
    }

    /**
     *          It returns seminar groups of the subject.
     *
     * @param   subject Subject
     * @return  Seminar groups.
     * @throws  NullPointerException if the subject is null.
     */
    public Set<Seminar> getSeminars (Subject subject)
    {
        if(subject == null)
            throw new NullPointerException("subject");
        Set<Seminar> ret = new LinkedHashSet<Seminar>();
        for(Seminar seminar : seminarToStudents.keySet())
        {
            if(!seminar.getSubject().equals(subject))
                continue;
            ret.add(seminar);
        }
        return ret;
    }

    /**
     *          It returns all registered students.
     *
     * @return  All registered students.
     */
    public Set<Student> getStudents()
    {
        return Collections.unmodifiableSet(studentToSeminar.keySet());
    }

    /**
     *          It returns students of the seminar group.
     *
     * @param   seminar Seminar.
     * @return  Students of the seminar group.
     * @throws  NullPointerException if the seminar is null.
     */
    public Set<Student> getStudents (Seminar seminar)
    {
        if(seminar == null)
            throw new NullPointerException("seminar");
        if(!seminarToStudents.containsKey(seminar))
            return Collections.unmodifiableSet(new LinkedHashSet<Student>());
        return Collections.unmodifiableSet(seminarToStudents.get(seminar));
    }

    /**
     *          It loads persistent data.
     */
    public void load ()
    {

    }

    /**
     *          It set presence of the student today.
     *
     * @param   student Student.
     * @param   seminar Seminar.
     * @throws  NullPointerException if some of the arguments is null.
     * @throws  AttendanceException if the seminar does not exist
     *          or the student is not registered in the seminar.     *
     */
    public void present (Student student, Seminar seminar)
            throws AttendanceException
    {
        present(student, seminar, clearDate());
    }

    /**
     *          It set presence of the student on specified day.
     *
     * @param   student Student.
     * @param   seminar Seminar.
     * @throws  NullPointerException if some of the arguments is null.
     * @throws  AttendanceException if the seminar does not exist
     *          or the student is not registered in the seminar.     *
     */
    public void present (Student student, Seminar seminar, Date date)
            throws AttendanceException
    {
        if(student == null)
            throw new NullPointerException("student");
        if(seminar == null)
            throw new NullPointerException("seminar");
        if(date == null)
            throw new NullPointerException("date");

        if(!studentToSeminar.keySet().contains(student))
            throw new AttendanceException("Student not registered");
        if(!seminarToStudents.keySet().contains(seminar))
            throw new AttendanceException("Seminar not registered");
        if(studentToSeminar.get(student) != seminar)
            throw new AttendanceException("Student not registered to this seminar");

        presences.get(student).add(clearDate(date));
    }

    /**
     *          It removes the seminar.
     *
     * @param   seminar Seminar
     * @return  TRUE if the seminar existed, otherwise FALSE.
     * @throws  NullPointerException if the seminar is null.
     */
    public boolean removeSeminar (Seminar seminar)
    {
        if(seminar == null)
            throw new NullPointerException("seminar");
        if(!seminarToStudents.keySet().contains(seminar))
            return false;
        for(Student student : seminarToStudents.get(seminar))
        {
            absences.remove(student);
            presences.remove(student);
            studentToSeminar.remove(student);
        }
        seminarToStudents.remove(seminar);
        return true;
    }

    /**
     *          It removes student from the seminar
     *
     * @param   student Student.
     * @param   seminar Seminar.
     * @return  TRUE if the student was registered in seminar,
     *          otherwise FALSE.
     * @throws  NullPointerException if some of the arguments is null.
     */
    public boolean removeStudent (Student student, Seminar seminar)
    {
        if(student == null)
            throw new NullPointerException("student");
        if(seminar == null)
            throw new NullPointerException("seminar");
        if(!seminarToStudents.keySet().contains(seminar))
            return false;
        if(!studentToSeminar.containsKey(student))
            return false;
        if(!studentToSeminar.get(student).equals(seminar))
            return false;
        return removeStudent(student);
    }

    /**
     *          It removes student from all seminars.
     *
     * @param   student Student
     * @return  TRUE if the student was registered in some seminar,
     *          otherwise FALSE.
     */
    public boolean removeStudent (Student student)
    {
        if(student == null)
            throw new NullPointerException("student");
        if(!studentToSeminar.containsKey(student))
            return false;
        seminarToStudents.get(studentToSeminar.get(student)).remove(student);
        studentToSeminar.remove(student);
        absences.remove(student);
        presences.remove(student);
        return true;
    }

    /**
     *          It saves data.
     */
    public void save ()
    {

    }
}
